Skip to content

feat: OTP/PIN input component - #2218

Draft
rkaraivanov wants to merge 7 commits into
masterfrom
rkaraivanov/pin-otp-input
Draft

feat: OTP/PIN input component#2218
rkaraivanov wants to merge 7 commits into
masterfrom
rkaraivanov/pin-otp-input

Conversation

@rkaraivanov

Copy link
Copy Markdown
Member

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that causes existing functionality to change)
  • Documentation update
  • Refactoring (code improvements without functional changes)

Checklist

  • My code follows the project's coding standards
  • I have tested my changes locally

- Refactored the pin input component to use event delegation for better performance and maintainability.
- Added edge case handling for focus events to ensure the correct input is focused and selected.
- Updated the storybook stories to reflect the changes in the component's API and behavior.
- Updated tests to cover the new event handling logic and edge cases.
Copilot AI review requested due to automatic review settings August 5, 2026 10:28

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new PIN/OTP input web component to the Ignite UI Web Components library, including theming, Storybook documentation, validation, and a comprehensive test suite. This extends the set of form-associated input controls while aligning with the project’s theming and validation-container patterns.

Changes:

  • Introduces IgcPinInputComponent with form association, validation, keyboard handling, paste support, and completion events.
  • Adds theme scaffolding (base + light/dark overrides + shared theme files) and exports the component from the package entrypoint.
  • Adds Storybook stories and new test utilities (paste + focusout simulation) and updates existing specs to use them.

Reviewed changes

Copilot reviewed 25 out of 25 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
stories/pin-input.stories.ts Adds Storybook stories for the new PIN/OTP input component.
src/index.ts Exports the new IgcPinInputComponent and its event map types.
src/components/pin-input/validators.ts Adds required-field validator for the PIN input.
src/components/pin-input/themes/themes.ts Registers light/dark theme style bundles for the component.
src/components/pin-input/themes/pin-input.base.scss Defines the base styling for the PIN input (layout, parts, states).
src/components/pin-input/themes/light/pin-input.shared.scss Light shared theme entry (placeholder for shared light overrides).
src/components/pin-input/themes/light/pin-input.bootstrap.scss Light Bootstrap overrides for the component.
src/components/pin-input/themes/light/pin-input.fluent.scss Light Fluent overrides for the component.
src/components/pin-input/themes/light/pin-input.indigo.scss Light Indigo overrides for the component.
src/components/pin-input/themes/light/pin-input.material.scss Light Material overrides for the component.
src/components/pin-input/themes/dark/pin-input.bootstrap.scss Dark Bootstrap overrides for the component.
src/components/pin-input/themes/dark/pin-input.fluent.scss Dark Fluent overrides for the component.
src/components/pin-input/themes/dark/pin-input.indigo.scss Dark Indigo overrides for the component.
src/components/pin-input/themes/dark/pin-input.material.scss Dark Material overrides for the component.
src/components/pin-input/themes/shared/pin-input.bootstrap.scss Shared Bootstrap sizing tokens for the component.
src/components/pin-input/themes/shared/pin-input.fluent.scss Shared Fluent sizing tokens for the component.
src/components/pin-input/themes/shared/pin-input.indigo.scss Shared Indigo sizing tokens for the component.
src/components/pin-input/themes/shared/pin-input.material.scss Shared Material sizing tokens for the component.
src/components/pin-input/pin-input.ts Implements the new igc-pin-input component (rendering, behavior, events, form integration).
src/components/pin-input/pin-input.spec.ts Adds tests for a11y, keyboard behavior, events, paste, grouping, and form association.
src/components/common/utils.spec.ts Adds reusable test helpers for focusout and paste event simulation.
src/components/common/controllers/key-bindings.ts Exposes Backspace and Delete key constants used by the new component tests/logic.
src/components/common/controllers/focus-ring.spec.ts Refactors to use the new simulateFocusOut helper.
src/components/carousel/carousel.spec.ts Refactors to use the new simulateFocusOut helper.
src/components/carousel/carousel-indicator-container.spec.ts Refactors to use the new simulateFocusOut helper.
Suppressed comments (2)

stories/pin-input.stories.ts:187

  • Storybook controls for value/separator are defined, but the Masked story doesn't bind them, so controls won't affect the rendered component in this story.
    <igc-pin-input
      .length=${length}
      .mode=${mode}
      ?mask=${mask}
      label=${ifDefined(label)}

stories/pin-input.stories.ts:216

  • Storybook controls for value/separator are defined, but the Alphanumeric story doesn't bind them, so controls won't affect the rendered component in this story.
    <igc-pin-input
      .length=${length}
      .mode=${mode}
      ?mask=${mask}
      label=${ifDefined(label)}


private _handlePaste(event: ClipboardEvent): void {
const index = this._getCellIndex(event);
const text = event.clipboardData?.getData('text');
Comment on lines +139 to +150
public set length(value: number) {
if (this._groups.length > 0) return;
const clamped = clamp(value, MIN_LENGTH, MAX_LENGTH);
if (clamped === this._length) return;

this._cells = Array.from(
{ length: clamped },
(_, i) => this._cells[i] ?? ''
);
this._length = clamped;
this._syncFormValue();
}
Comment on lines +195 to +209
public set groups(value: number[]) {
this._groups = value;
if (!value.length) return;
const clamped = clamp(
value.reduce((a, b) => a + b, 0),
MIN_LENGTH,
MAX_LENGTH
);
this._cells = Array.from(
{ length: clamped },
(_, i) => this._cells[i] ?? ''
);
this._length = clamped;
this._syncFormValue();
}
display: block;
position: relative;

--_cell-size: #{rem(48px)};
Comment on lines +441 to +443
const clipboardData = new DataTransfer();
clipboardData.setData('text/plain', pastedText);

});

describe('Validation container slots', () => {
it('', async () => {
Comment on lines +142 to +164
render: ({
length,
mode,
mask,
label,
placeholder,
required,
disabled,
invalid,
name,
}) => html`
<igc-pin-input
.length=${length}
.mode=${mode}
?mask=${mask}
label=${ifDefined(label)}
placeholder=${ifDefined(placeholder)}
?required=${required}
?disabled=${disabled}
?invalid=${invalid}
name=${ifDefined(name)}
></igc-pin-input>
`,
Comment on lines +340 to +349
if (filtered && filtered !== prev) {
const value = this._cellsValue;
this._emitInputEvent(value);

if (index < this._length - 1) {
this._focusCell(index + 1);
}

this._emitCompleteIfFull(value);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants